# This file is part of KEmuFuzzer.
# 
# KEmuFuzzer is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# 
# KEmuFuzzer is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along with
# KEmuFuzzer.  If not, see <http://www.gnu.org/licenses/>.

import os, sys

DEBUG_MODE=False

INT_OUTPUT_PORT = 0xef
HEADER_FILENAME = "interrupts.h"
BODY_FILENAME   = "interrupts.c"
INTWRP_FILENAME = "int_wrappers.S"
INTERRUPTS=64
EXC_WITH_ERRCODE = [8, 10, 11, 12, 13, 14, 17]

def gen_header(n):
    s = """\
// Warning: this file has been automatically generated by %s

#ifndef _INTERRUPTS_H
#define _INTERRUPTS_H

#include <stdint.h>
#include <sys/interrupt.h>

void set_interrupt_handlers(idte_t *idt, uint16_t seg);

""" % sys.argv[0]
    for i in range(32):
        s += "void int_handler_%d(uint32_t, uint32_t, uint32_t, uint32_t);\n" % i
        s += "void int_wrapper_%d(void);\n" % i

    s += "void int_handler_null(void);\n"

    s += """\

#endif	/* _INTERRUPTS_H */
"""
    return s

def gen_body(n):
    s = """\
// Warning: this file has been automatically generated by %s

#include "interrupts.h"

extern int kprintf(const char *format, ...);

static void set_idt_entry(idte_t *idt, uint8_t n, uint16_t seg, uint32_t off)
{
  idt[n].off_0_15 = off & 0xffff;
  idt[n].off_16_31 = (off>>16) & 0xffff;
  idt[n].seg = seg;
  idt[n].zero = 0;
  idt[n].type = 0xe; /* interrupt gate */
  idt[n].dpl = 3;
  idt[n].present = 1;
}

void set_interrupt_handlers(idte_t *pidt, uint16_t seg)
{
"""  % sys.argv[0]

    for i in range (32):
        if DEBUG_MODE:
            s += "  kprintf(\"handler #%.2x address: 0x%%08x\\n\", int_wrapper_%d);\n" % (i, i)
        s += "  set_idt_entry(pidt, %d, seg, (uint32_t)int_wrapper_%d);\n" % (i, i)

    for i in range (32, n):
        s += "  set_idt_entry(pidt, %d, seg, (uint32_t)int_handler_null);\n" % i

    s += "}\n\n"

    for i in range(32):
        s += """\
void int_handler_%d(uint32_t code, uint32_t eip, uint32_t cs, uint32_t eflags)
{
  kprintf("interrupt handler %d speaking\\n");
  return;
}
""" % (i, i)
    return s

def gen_wrap(n, port):
    s = """\
# Warning: this file has been automatically generated by %s

.extern tc_ring0_base
.extern handler_stack_errcode, handler_stack_eip, handler_stack_cs, handler_stack_eflags
.extern handler_stack_esp, handler_stack_ss
.extern original_esp

"""  % sys.argv[0]

    for i in range (32):
        if i == 31:
            exc = [0xFF, 0xFF]
        else:
            exc = [i, 0]
        s += """\
.globl int_wrapper_%d
.type  int_wrapper_%d, @function

int_wrapper_%d:
  push %%eax;
  mov %%dr7, %%eax;
  xchg %%eax, (%%esp);

  jmp notify_int%d;
notify_int%d:
  outb %%al, $0x%.2x;
  .byte 0xeb;   .byte 0x02;
  .byte 0x%02x; .byte 0x%02x;

  # Dump CPU state
  push %%eax;

  # Switch segments
  mov $0x48, %%eax;
  mov %%ax, %%ds;
"""  % (i, i, i, i, i, port, exc[0], exc[1])

        if i in EXC_WITH_ERRCODE:
            s += """\
  mov 0x08(%esp), %eax;
  mov %eax, handler_stack_errcode;

  mov 0x0c(%esp), %eax;
  mov %eax, handler_stack_eip;

  mov 0x10(%esp), %eax;
  mov %eax, handler_stack_cs;

  mov 0x14(%esp), %eax;
  mov %eax, handler_stack_eflags;
"""
        else:
            s += """\
  movl $0x0, handler_stack_errcode;

  mov 0x08(%esp), %eax;
  mov %eax, handler_stack_eip;

  mov 0x0c(%esp), %eax;
  mov %eax, handler_stack_cs;

  mov 0x10(%esp), %eax;
  mov %eax, handler_stack_eflags;
"""

        s += """\
  mov $0x50, %%eax;
  mov %%ax, %%ss;

  # Restore pushed eax value from the old stack
  add tc_ring0_base, %%esp;
  mov (%%esp), %%eax;
  sub tc_ring0_base, %%esp;

  # Move %%esp to a safe position
  mov %%esp, original_esp;
  # We have to adjust %%esp to get rid of 2 "push" instructions
  add $0x8, original_esp;

  lea stack_r0, %%esp;
  pushl $0x%.8x;
  call dump_state;

  # Restore %%esp
  mov original_esp, %%esp;

  hlt;
  iret;

""" % i


    s += """\
.globl int_handler_null
.type  int_handler_null, @function

int_handler_null:
  iret;

"""

    return s

def main(port):
    h = gen_header(INTERRUPTS)
    b = gen_body(INTERRUPTS)
    c = gen_wrap(INTERRUPTS, port)

    f = open(HEADER_FILENAME, 'w')
    f.write(h)
    f.close()

    f = open(BODY_FILENAME, 'w')
    f.write(b)
    f.close()

    f = open(INTWRP_FILENAME, 'w')
    f.write(c)
    f.close()

if __name__ == "__main__":
    main(int(sys.argv[1]))
